package com.bite.friend.service.user.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bit.common.redis.service.RedisService;
import com.bite.api.domain.vo.UserQuestionResultVO;
import com.bite.common.core.constants.CacheConstants;
import com.bite.common.core.constants.Constants;
import com.bite.common.core.domain.LoginUser;
import com.bite.common.core.domain.vo.LoginUserVO;
import com.bite.common.core.enums.ResultCode;
import com.bite.common.core.enums.UserIdentity;
import com.bite.common.core.utils.ThreadLocalUtil;
import com.bite.common.message.utils.SMSSender;
import com.bite.common.security.exception.ServiceException;
import com.bite.common.security.service.TokenService;
import com.bite.friend.domain.user.User;
import com.bite.friend.domain.user.dto.UserDTO;
import com.bite.friend.domain.user.dto.UserEditDTO;
import com.bite.friend.domain.user.vo.UserVO;
import com.bite.friend.manager.UserCacheManager;
import com.bite.friend.mapper.user.UserMapper;
import com.bite.friend.service.user.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private SMSSender smsSender;

    @Autowired
    private RedisService redisService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private UserCacheManager userCacheManager;

    @Value("${sms.spug.params.ttl:5}")
    private Long ttl;

    @Value("${sms.spug.params.limitTime:5}")
    private Long limitTime;

    @Value("${jwt.secret}")
    private String secret;

    @Value("${sms.is-send:false}")
    private boolean isSend;

    @Value("${file.oss.uploadUrl}")
    private String uploadUrl;

    @Override
    public boolean sendCode(UserDTO userDTO) {
        String phone = userDTO.getPhone();

        // 校验 - 发送 - 存储
        checkPhone(phone);
        String codeKey = getPhoneCodeKey(phone);
        String codeTimeKey = getCodeTimeKey(phone);
        // 先判断时间是不是已经过去 1分钟
        Long expire = redisService.getExpire(codeKey, TimeUnit.SECONDS);
        if (expire != null && (ttl * 60 - expire) < 60) {
            throw new ServiceException(ResultCode.FAILED_SEND_FREQUENT);
        }
        // 再判断是否超过每日限制次数
        Long codeTime = redisService.getCacheObject(codeTimeKey, Long.class);
        if (codeTime != null && codeTime >= limitTime) {
            throw new ServiceException(ResultCode.FAILED_REACH_LIMIT_TIME);
        }


        String code = isSend ? RandomUtil.randomNumbers(6) : Constants.DEFAULT_CODE;
        if (isSend) {
            boolean result = smsSender.sendWechat(code, phone, ttl);
            if (!result) throw new ServiceException(ResultCode.FAILED_SEND_CODE);
        }

        // 发送成功 存储起来 并计数 如果是第一次 那么需要限制一下时间
        redisService.setCacheObject(codeKey, code, ttl, TimeUnit.MINUTES);
        redisService.increment(codeTimeKey);
        if (codeTime == null) {
            long second = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(codeTimeKey, second, TimeUnit.SECONDS);
        }
        return true;
    }

    @Override
    public String codeLogin(UserDTO userDTO) {
        String phone = userDTO.getPhone();
        String code = userDTO.getCode();
        checkPhone(phone);
        checkCode(phone, code);
        // 验证通过
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().select(User::getUserId, User::getAvatar, User::getNickName).eq(User::getPhone, phone));

        LoginUser loginUser;
        if (user == null) {
            // 注册
            user = User.createByPhone(phone);
            user.setCreateBy(Constants.SYSTEM_ID);
            userMapper.insert(user);
        }
        // 生成token

//        loginUser = new LoginUser(UserIdentity.ORDINARY, user.getNickName());
        loginUser = new LoginUser();
        BeanUtil.copyProperties(user, loginUser);
        loginUser.setIdentity(UserIdentity.ORDINARY.getValue());
        return tokenService.createToken(user.getUserId(), secret, loginUser);
    }

    @Override
    public boolean logout(String token) {
        return tokenService.delLoginUser(token, secret, UserIdentity.ORDINARY);
    }

    @Override
    public LoginUserVO info(String token) {
        LoginUserVO loginUserVO = tokenService.getLoginUserVO(token, secret);
        if (loginUserVO == null) {
            throw new ServiceException(ResultCode.FAILED);
        }
        if (StrUtil.isNotEmpty(loginUserVO.getAvatar())) {
            loginUserVO.setAvatar(uploadUrl + loginUserVO.getAvatar());
        }
        return loginUserVO;
    }

    @Override
    public UserVO detail() {
        User user = getUser();
//        privacySetting(user);
        UserVO userVO = new UserVO();
        BeanUtil.copyProperties(user, userVO);
        if (StrUtil.isNotEmpty(userVO.getAvatar())) {
            userVO.setAvatar(uploadUrl + userVO.getAvatar());
        }
        return userVO;
    }

    @Override
    public int edit(UserEditDTO userEditDTO) {
        User oldUser = getUser();
        User user = User.createByDTO(userEditDTO);
        user.setAvatar(oldUser.getAvatar());
        return refreshCacheAndDatabase(user);
    }


    @Override
    public int avatarUpdate(UserEditDTO userEditDTO) {
        User user = getUser();
        user.setAvatar(userEditDTO.getAvatar());
        return refreshCacheAndDatabase(user);
    }

    private User getUser() {
        Long userId = getUserIdByLocal();
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userCacheManager.getByUserId(userId);
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
}
        return user;
    }

    private int refreshCacheAndDatabase(User user) {
        // Redis + MySQL + 当前登录用户的缓存 都修改
        LoginUser loginUser = new LoginUser();
        loginUser.setAvatar(user.getAvatar());
        loginUser.setNickName(user.getNickName());
        tokenService.refreshLoginUser(getUserUUIDByLocal(), loginUser);

        userCacheManager.refreshUser(user);
        return userMapper.updateById(user);
    }

    // 隐私设计 将密码、邮箱、手机号等设置为**
    private void privacySetting(User user) {
        String replace = "******";
        String password = user.getPassword();
        if (StrUtil.isNotEmpty(password)) {
            user.setPassword(replace);
        }
        String email = user.getEmail();
        if (StrUtil.isNotEmpty(email)) {
            int index = email.lastIndexOf('@');
            if (index != -1) {
                String newEmail = email.substring(0, 3) + replace + email.substring(index);
                user.setEmail(newEmail);
            }
        }
        String phone = user.getPhone();
        if (StrUtil.isNotEmpty(phone)) {
            String newPhone = phone.substring(0, 3) + replace + phone.substring(7, 11);
            user.setPhone(newPhone);
        }
    }

    // 查看 如果正确 那么就删除并返回true
    private boolean checkCode(String phone, String code) {
        String phoneCodeKey = getPhoneCodeKey(phone);
        String cacheCode = redisService.getCacheObject(phoneCodeKey, String.class);
        if (StrUtil.isEmpty(cacheCode)) {
            throw new ServiceException(ResultCode.FAILED_CODE_INVALID);
        }
        if (!cacheCode.equals(code)) {
            throw new ServiceException(ResultCode.FAILED_CODE_ERROR);
        }
        //
        return redisService.deleteObject(phoneCodeKey);
    }

    // 检查手机号
    private boolean checkPhone(String phone) {
        Pattern pattern = Pattern.compile("^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$");
        Matcher matcher = pattern.matcher(phone);
        if (!matcher.matches()) {
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }
        return true;
    }

    private String getPhoneCodeKey(String phone) {
        return CacheConstants.PHONE_CODE_KEY + phone;
    }

    private String getCodeTimeKey(String phone) {
        return CacheConstants.CODE_TIME_KEY + phone;
    }

    private Long getUserIdByLocal() {
        return ThreadLocalUtil.get(Constants.THREAD_LOCAL_USER_ID, Long.class);
    }

    private String getUserUUIDByLocal() {
        return ThreadLocalUtil.get(Constants.THREAD_LOCAL_USER_UUID, String.class);
    }
}
